package com.elysia.gulimall.search.service.impl;

import com.alibaba.fastjson.JSON;
import com.elysia.common.to.es.SkuEsModel;
import com.elysia.gulimall.search.config.GulimallElasticSearchConfig;
import com.elysia.gulimall.search.constant.EsConstant;
import com.elysia.gulimall.search.service.MallSearchSearchService;
import com.elysia.gulimall.search.vo.SearchParam;
import com.elysia.gulimall.search.vo.SearchResult;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.*;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.nested.Nested;
import org.elasticsearch.search.aggregations.bucket.nested.NestedAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.stream.Collectors;

@Service("mallSearchSearchService")
public class MallSearchServiceImpl implements MallSearchSearchService {
    @Autowired
    private RestHighLevelClient client;
    @Override
    public SearchResult search(SearchParam searchParam) {
        //构建search语句
        SearchRequest searchRequest = buildSearchRequest(searchParam);

        SearchResponse searchResponse = null;
        try {
            searchResponse = client.search(searchRequest, GulimallElasticSearchConfig.COMMON_OPTIONS);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        return buildSearchResult(searchResponse,searchParam);
    }

    /**
     * 构建DSL语句
     * 模糊匹配，过滤（按照属性，分类，品牌，价格区间，库存），排序，分页，高亮，聚合分析
     * @param searchParam
     * @return
     */
    private SearchRequest buildSearchRequest(SearchParam searchParam) {
        SearchSourceBuilder searchSourceBuilder =new SearchSourceBuilder();
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        /**
         * 模糊匹配
         */
        if(!StringUtils.isEmpty(searchParam.getKeyword())){
            boolQueryBuilder.must(QueryBuilders.matchQuery("skuTitle",searchParam.getKeyword()));
        }
        if(searchParam.getCatalog3Id()!=null){
            boolQueryBuilder.filter(QueryBuilders.termQuery("catalogId",searchParam.getCatalog3Id()));
        }
        if(searchParam.getBrandId()!=null&& !searchParam.getBrandId().isEmpty()){
            boolQueryBuilder.filter(QueryBuilders.termsQuery("brandId",searchParam.getBrandId()));
        }
        if(searchParam.getHasStock()!=null){
            boolQueryBuilder.filter(QueryBuilders.termQuery("hasStock",searchParam.getHasStock()==1));
        }
        /**
         * 价格区间
         */
        if(!StringUtils.isEmpty(searchParam.getSkuPrice())){
            RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery("skuPrice");
            String[] price = searchParam.getSkuPrice().trim().split("_");
            if(price.length==2){
                //这里 skuPrice=_1000 会传 空字符串
                rangeQueryBuilder.gte(StringUtils.isEmpty(price[0])? null:price[0]).lte(price[1]);
            }else if(price.length == 1){
                if(searchParam.getSkuPrice().startsWith("_")){
                    rangeQueryBuilder.lte(price[0]);
                }
                if(searchParam.getSkuPrice().endsWith("_")){
                    rangeQueryBuilder.gte(price[0]);
                }
            }
            boolQueryBuilder.filter(rangeQueryBuilder);
        }
        /**
         * 属性
         * 使用nested嵌套查询
         */
        if(searchParam.getAttrs() != null && !searchParam.getAttrs().isEmpty()){
            searchParam.getAttrs().forEach(item -> {
                BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
                //attrs=1_5寸:8寸&2_16G:8G
                String[] s = item.split("_");
                String attrId=s[0];
                String[] attrValues = s[1].split(":");//这个属性检索用的值
                boolQuery.must(QueryBuilders.termQuery("attrs.attrId",attrId));
                boolQuery.must(QueryBuilders.termsQuery("attrs.attrValue",attrValues));

                NestedQueryBuilder nestedQueryBuilder = QueryBuilders.nestedQuery("attrs",boolQuery, ScoreMode.None);
                boolQueryBuilder.filter(nestedQueryBuilder);
            });
        }
        searchSourceBuilder.query(boolQueryBuilder);
        /**
         * 排序，分页，高亮
         */
        //排序 形式为sort=hotScore_asc/desc
        if(!StringUtils.isEmpty(searchParam.getSort())){
            String sort = searchParam.getSort();
            String[] sortFields = sort.split("_");

            SortOrder sortOrder="asc".equalsIgnoreCase(sortFields[1])? SortOrder.ASC:SortOrder.DESC;

            searchSourceBuilder.sort(sortFields[0],sortOrder);
        }

        //分页
        searchSourceBuilder.from((searchParam.getPageNum()-1) *EsConstant.PRODUCT_PAGESIZE);
        searchSourceBuilder.size(EsConstant.PRODUCT_PAGESIZE);

        //高亮
        if(!StringUtils.isEmpty(searchParam.getKeyword())){

            HighlightBuilder highlightBuilder = new HighlightBuilder();
            highlightBuilder.field("skuTitle");
            highlightBuilder.preTags("<b style='color:red'>");
            highlightBuilder.postTags("</b>");

            searchSourceBuilder.highlighter(highlightBuilder);
        }

        /**
         * 聚合分析
         */
        //1. 按照品牌进行聚合
        TermsAggregationBuilder brand_agg = AggregationBuilders.terms("brand_agg");
        brand_agg.field("brandId").size(50);

        //1.1 品牌的子聚合-品牌名聚合
        brand_agg.subAggregation(AggregationBuilders.terms("brand_name_agg")
                .field("brandName").size(1));
        //1.2 品牌的子聚合-品牌图片聚合
        brand_agg.subAggregation(AggregationBuilders.terms("brand_img_agg")
                .field("brandImg").size(1));
        searchSourceBuilder.aggregation(brand_agg);

        //2. 按照分类信息进行聚合
        TermsAggregationBuilder catalog_agg = AggregationBuilders.terms("catalog_agg");
        catalog_agg.field("catalogId").size(20);
        catalog_agg.subAggregation(AggregationBuilders.terms("catalog_name_agg").field("catalogName").size(1));
        searchSourceBuilder.aggregation(catalog_agg);

        //2. 按照属性信息进行聚合
        NestedAggregationBuilder attr_agg = AggregationBuilders.nested("attr_agg", "attrs");
        //2.1 按照属性ID进行聚合
        TermsAggregationBuilder attr_id_agg = AggregationBuilders.terms("attr_id_agg").field("attrs.attrId");
        attr_agg.subAggregation(attr_id_agg);
        //2.1.1 在每个属性ID下，按照属性名进行聚合
        attr_id_agg.subAggregation(AggregationBuilders.terms("attr_name_agg").field("attrs.attrName").size(1));
        //2.1.1 在每个属性ID下，按照属性值进行聚合
        attr_id_agg.subAggregation(AggregationBuilders.terms("attr_value_agg").field("attrs.attrValue").size(50));
        searchSourceBuilder.aggregation(attr_agg);

        System.out.println("DSL:");
        System.out.println(searchSourceBuilder.toString());
        return new SearchRequest(new String[]{EsConstant.PRODUCT_INDEX},searchSourceBuilder);
    }

    /**
     * 构建响应数据
     * 1.商品.标题高亮
     * 2.品牌,分类，页码
     * @param searchResponse
     * @return
     */
    private SearchResult buildSearchResult(SearchResponse searchResponse,SearchParam searchParam) {
        SearchResult result = new SearchResult();
        /**
         * 1.将source转为sku
         * 2.替换标题，高亮
         * 3.将sku商品集合装入result
         */
        SearchHits hits = searchResponse.getHits();
        List<SkuEsModel> products =  new ArrayList<>();
        for (SearchHit hit:hits.getHits()) {
            SkuEsModel skuEsModel = JSON.parseObject(hit.getSourceAsString(), SkuEsModel.class);
            if(!StringUtils.isEmpty(searchParam.getKeyword())){
                String skuTitle = hit.getHighlightFields().get("skuTitle").getFragments()[0].string();
                skuEsModel.setSkuTitle(skuTitle);
            }
            products.add(skuEsModel);
        }
        result.setProduct(products);
        /**
         * 页码
         */
        result.setTotal(hits.getTotalHits().value);
        result.setTotalPages((int)hits.getTotalHits().value%EsConstant.PRODUCT_PAGESIZE == 0?
                (int)hits.getTotalHits().value/EsConstant.PRODUCT_PAGESIZE:(int)hits.getTotalHits().value/EsConstant.PRODUCT_PAGESIZE+1);
        result.setPageNum(searchParam.getPageNum());

        List<Integer> pageNavs = new ArrayList<>();
        for (int i = 1; i <= result.getTotalPages(); i++) {
            pageNavs.add(i);
        }
        result.setPageNavs(pageNavs);

        Aggregations aggregations = searchResponse.getAggregations();
        /**
         * 品牌
         */
        HashMap<Long, SearchResult.BrandVo> brandVoHashMap = new HashMap<>();
        Terms brand_agg =  aggregations.get("brand_agg");
        List<SearchResult.BrandVo> brands =new ArrayList<>();
        for(Terms.Bucket bucket:brand_agg.getBuckets()){
            SearchResult.BrandVo brand = new SearchResult.BrandVo();
            brand.setBrandId(bucket.getKeyAsNumber().longValue());
            //品牌名
            Terms brand_name_agg = bucket.getAggregations().get("brand_name_agg");
            brand.setBrandName(brand_name_agg.getBuckets().get(0).getKeyAsString());
            //品牌img
            Terms brand_img_agg = bucket.getAggregations().get("brand_img_agg");
            brand.setBrandImg(brand_img_agg.getBuckets().get(0).getKeyAsString());
            brands.add(brand);
            brandVoHashMap.put(brand.getBrandId(),brand);
        }
        result.setBrands(brands);
        /**
         * 分类
         */
        Terms catalog_agg =  aggregations.get("catalog_agg");
        List<SearchResult.CatalogVo> catalogs =new ArrayList<>();
        for(Terms.Bucket bucket:catalog_agg.getBuckets()){
            SearchResult.CatalogVo catalog = new SearchResult.CatalogVo();
            catalog.setCatalogId(bucket.getKeyAsNumber().longValue());
            //分类名
            Terms catalog_name_agg = bucket.getAggregations().get("catalog_name_agg");
            catalog.setCatalogName(catalog_name_agg.getBuckets().get(0).getKeyAsString());
            catalogs.add(catalog);
        }
        result.setCatalogs(catalogs);
        /**
         * 当前商品涉及到的所有属性信息
         */
        List<SearchResult.AttrVo> attrVos = new ArrayList<>();
        HashMap<Long,SearchResult.AttrVo> attrVoHashMap = new HashMap<>();
        //获取属性信息的聚合
        Nested attr_agg = aggregations.get("attr_agg");
        Terms attr_id_agg = attr_agg.getAggregations().get("attr_id_agg");
        for (Terms.Bucket bucket : attr_id_agg.getBuckets()) {
            SearchResult.AttrVo attrVo = new SearchResult.AttrVo();
            //1、得到属性的id
            long attrId = bucket.getKeyAsNumber().longValue();
            attrVo.setAttrId(attrId);
            //2、得到属性的名字
            Terms attr_name_agg = bucket.getAggregations().get("attr_name_agg");
            String attrName = attr_name_agg.getBuckets().get(0).getKeyAsString();
            attrVo.setAttrName(attrName);
            //3、得到属性的所有值
            Terms attr_value_agg = bucket.getAggregations().get("attr_value_agg");
            List<String> attrValues = attr_value_agg.getBuckets().stream().map(item -> item.getKeyAsString()).collect(Collectors.toList());
            attrVo.setAttrValue(attrValues);
            attrVos.add(attrVo);
            /*
            这里用hashMap存一下属性，就不用远程调用了，太麻烦了
             */
            attrVoHashMap.put(attrId,attrVo);
        }
        result.setAttrs(attrVos);

        //6、构建面包屑导航
        if (searchParam.getAttrs() != null && !searchParam.getAttrs().isEmpty()) {
            List<SearchResult.NavVo> collect = searchParam.getAttrs().stream().map(attr -> {
                //1、分析每一个attrs传过来的参数值
                SearchResult.NavVo navVo = new SearchResult.NavVo();
                String[] s = attr.split("_");
                navVo.setNavValue(s[1]);
                navVo.setNavId(Long.parseLong(s[0]));
                navVo.setParamName("attrs");
                String navName;
                if (!StringUtils.isEmpty(navName=attrVoHashMap.get(Long.parseLong(s[0])).getAttrName())) {
                    navVo.setNavName(navName);
                } else {
                    navVo.setNavName(s[0]);
                }
                return navVo;
            }).collect(Collectors.toList());
            result.setNavs(collect);
        }

        if(searchParam.getBrandId()!=null && !searchParam.getBrandId().isEmpty()){
            List<SearchResult.NavVo> navs = result.getNavs();
            for (Long brandId:searchParam.getBrandId()) {
                SearchResult.NavVo navVo = new SearchResult.NavVo();
                navVo.setParamName("brandId");
                navVo.setNavId(brandId);
                navVo.setNavName("品牌");
                String navValue;
                if (!StringUtils.isEmpty(navValue=brandVoHashMap.get(brandId).getBrandName())) {
                    navVo.setNavValue(navValue);
                } else {
                    navVo.setNavValue(String.valueOf(brandId));
                }
                navs.add(0,navVo);
            }
        }

        System.out.println("searchResult:");
        System.out.println(result);
        return result;
    }

}
